NexusPi Git Node
TcpInterface.h copilot/add-rtnode-configuration-options (9529fa8a) Text, 20.25 KB
T8b949e// Copyright (C) 2026, Boundary Mode Extension
T8b949e// Based on microReticulum_Firmware by Mark Qvist
T8b949e//
T8b949e// TcpInterface — An RNS InterfaceImpl that bridges a WiFi TCP
T8b949e// connection as an RNS transport interface. Used for both the
T8b949e// backbone (BackboneInterface) and local AP (LocalTcpInterface).
T8b949e//
T8b949e// This program is free software: you can redistribute it and/or modify
T8b949e// it under the terms of the GNU General Public License as published by
T8b949e// the Free Software Foundation, either version 3 of the License, or
T8b949e// (at your option) any later version.
Tff7b72#Tff7b72ifndef TCP_INTERFACE_H
Tff7b72#Tff7b72define TCP_INTERFACE_H
Tff7b72#Tff7b72ifdef HAS_RNS
Tff7b72#Tff7b72ifdef BOUNDARY_MODE
Tff7b72#Tff7b72include T8b949e<WiFi.h>
Tff7b72#Tff7b72include T8b949e<lwip/sockets.h>T8b949e // SO_LINGER — force RST to free lwIP PCBs immediately
Tff7b72#Tff7b72include T8b949e<Interface.h>
Tff7b72#Tff7b72include T8b949e<Transport.h>
Tff7b72#Tff7b72include T8b949e<Bytes.h>
T8b949e// ─── TCP Interface Configuration ─────────────────────────────────────────────
Tff7b72#Tff7b72define TCP_IF_DEFAULT_PORT 4242
Tff7b72#Tff7b72ifdef BOUNDARY_MODE
Tff7b72#Tff7b72define TCP_IF_MAX_CLIENTS 8
Tff7b72#Tff7b72else
Tff7b72#Tff7b72define TCP_IF_MAX_CLIENTS 4
Tff7b72#Tff7b72endif
Tff7b72#Tff7b72define TCP_IF_HW_MTU 1064
Tff7b72#Tff7b72define TCP_IF_CONNECT_TIMEOUT 6000 T8b949e// ms
Tff7b72#Tff7b72define TCP_IF_WRITE_TIMEOUT 2000 T8b949e// ms — short to avoid WDT
Tff7b72#Tff7b72define TCP_IF_READ_TIMEOUT 120000 T8b949e// ms — 2 minutes (backbone can go quiet)
Tff7b72#Tff7b72define TCP_IF_RECONNECT_MIN 10000 T8b949e// ms — initial reconnect interval
Tff7b72#Tff7b72define TCP_IF_RECONNECT_MAX 120000 T8b949e// ms — max backoff (2 minutes)
Tff7b72#Tff7b72define TCP_IF_KEEPALIVE_INTERVAL 30000 T8b949e// ms — send empty HDLC frames to keep link alive
Tff7b72#Tff7b72define TCP_IF_POLL_INTERVAL 10 T8b949e// ms
T8b949e// HDLC-like framing for TCP (matches Reticulum-rust tcp_interface)
Tff7b72#Tff7b72define HDLC_FLAG 0x7E
Tff7b72#Tff7b72define HDLC_ESC 0x7D
Tff7b72#Tff7b72define HDLC_ESC_MASK 0x20
T8b949e// ─── TCP Interface Mode ──────────────────────────────────────────────────────
Tff7b72enum Te6edf3TcpIfMode Tb4b4b4{
Te6edf3TCP_IF_MODE_SERVER Tff7b72= T79c0ff0Tb4b4b4, T8b949e// Listen for incoming connections (from backbone rnsd)
Te6edf3TCP_IF_MODE_CLIENT Tff7b72= T79c0ff1Tb4b4b4, T8b949e// Connect out to a backbone rnsd TCP server
Tb4b4b4}Tb4b4b4;
T8b949e// ─── Client connection state ─────────────────────────────────────────────────
Tff7b72struct T56d364TcpClient Tb4b4b4{
Te6edf3WiFiClient Te6edf3clientTb4b4b4;
Tffa657uint32_t Te6edf3last_activityTb4b4b4;
Tffa657bool Te6edf3activeTb4b4b4;
T8b949e// HDLC deframe state
Tffa657bool Te6edf3in_frameTb4b4b4;
Tffa657bool Te6edf3escapeTb4b4b4;
Tffa657bool Te6edf3truncatedTb4b4b4;
Tffa657uint8_t Te6edf3rxbufTb4b4b4[Te6edf3TCP_IF_HW_MTUTb4b4b4]Tb4b4b4;
Tffa657uint16_t Te6edf3rxlenTb4b4b4;
Tb4b4b4}Tb4b4b4;
T8b949e// ─── TcpInterface Class ─────────────────────────────────────────────────────
Te6edf3class Te6edf3TcpInterface Tff7b72: Te6edf3public Te6edf3RNSTff7b72:Tff7b72:Te6edf3InterfaceImpl Tb4b4b4{
Te6edf3publicTff7b72:
Te6edf3TcpInterfaceTb4b4b4(Te6edf3TcpIfMode Te6edf3modeTb4b4b4, Tffa657uint16_t Te6edf3port Tff7b72= Te6edf3TCP_IF_DEFAULT_PORTTb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3target_host Tff7b72= Tffa657nullptrTb4b4b4, Tffa657uint16_t Te6edf3target_port Tff7b72= T79c0ff0Tb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffBackboneInterfaceTa5d6ff"Tb4b4b4)
Tff7b72: Te6edf3RNSTff7b72:Tff7b72:Te6edf3InterfaceImplTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4,
Te6edf3_modeTb4b4b4(Te6edf3modeTb4b4b4)Tb4b4b4,
Te6edf3_portTb4b4b4(Te6edf3portTb4b4b4)Tb4b4b4,
Te6edf3_target_portTb4b4b4(Te6edf3target_portTb4b4b4)Tb4b4b4,
Te6edf3_serverTb4b4b4(Tffa657nullptrTb4b4b4)Tb4b4b4,
Te6edf3_num_clientsTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_last_reconnectTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_last_keepaliveTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_reconnect_intervalTb4b4b4(Te6edf3TCP_IF_RECONNECT_MINTb4b4b4)Tb4b4b4,
Te6edf3_read_timeoutTb4b4b4(Te6edf3TCP_IF_READ_TIMEOUTTb4b4b4)Tb4b4b4,
Te6edf3_resolved_ipTb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_consecutive_failuresTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_startedTb4b4b4(Tffa657falseTb4b4b4)
Tb4b4b4{
Te6edf3_IN Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_OUT Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_HW_MTU Tff7b72= Te6edf3TCP_IF_HW_MTUTb4b4b4;
T8b949e// v1.0.12: Tell Transport this interface has a known fixed MTU,
T8b949e// enabling link MTU clamping when forwarding LINKREQUEST packets.
Te6edf3_FIXED_MTU Tff7b72= Tffa657trueTb4b4b4;
T8b949e// TCP links are effectively 10 Mbps+. Setting a realistic
T8b949e// bitrate lets Transport prefer TCP paths over LoRa when
T8b949e// both exist for the same destination.
T8b949e// announce_cap = 2% keeps backbone announce flooding in check.
Te6edf3_bitrate Tff7b72= T79c0ff10000000Tb4b4b4;
Te6edf3_announce_cap Tff7b72= T79c0ff2.0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3target_host Tff7b72!Tff7b72= Tffa657nullptrTb4b4b4) Tb4b4b4{
Te6edf3strncpyTb4b4b4(Te6edf3_target_hostTb4b4b4, Te6edf3target_hostTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3_target_hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3_target_hostTb4b4b4[Tff7b72sizeofTb4b4b4(Te6edf3_target_hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3_target_hostTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Te6edf3virtual Tff7b72~Te6edf3TcpInterfaceTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// ─── Lifecycle ───────────────────────────────────────────────────────────
Tffa657bool Te6edf3startTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_startedTb4b4b4) Tff7b72return Tffa657trueTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_SERVERTb4b4b4) Tb4b4b4{
Te6edf3_server Tff7b72= Te6edf3new Te6edf3WiFiServerTb4b4b4(Te6edf3_portTb4b4b4, Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4)Tb4b4b4;
Te6edf3_serverTff7b72-Tff7b72>Te6edf3beginTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_serverTff7b72-Tff7b72>Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Server listening on port %dTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_portTb4b4b4)Tb4b4b4;
Te6edf3_started Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Client mode — try initial connection
Te6edf3_started Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_connect_clientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72return Te6edf3_startedTb4b4b4;
Tb4b4b4}
Tffa657void Te6edf3stopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tb4b4b4{
T8b949e// Force RST to free lwIP PCBs immediately (no TIME_WAIT)
Tffa657int Te6edf3fd Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3fdTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fd Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72struct T56d364linger Te6edf3linTb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_onoff Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_linger Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_LINGERTb4b4b4, Tff7b72&Te6edf3linTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3linTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3WiFiClientTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3_serverTb4b4b4) Tb4b4b4{
Te6edf3_serverTff7b72-Tff7b72>Te6edf3endTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3delete Te6edf3_serverTb4b4b4;
Te6edf3_server Tff7b72= Tffa657nullptrTb4b4b4;
Tb4b4b4}
Te6edf3_started Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_num_clients Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
T8b949e// ─── Main loop — call from Arduino loop() ────────────────────────────────
Tffa657void Te6edf3loopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_startedTb4b4b4) Tff7b72returnTb4b4b4;
T8b949e// Accept new connections in server mode
Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_SERVER Tff7b72&Tff7b72& Te6edf3_serverTb4b4b4) Tb4b4b4{
Te6edf3WiFiClient Te6edf3newClient Tff7b72= Te6edf3_serverTff7b72-Tff7b72>Te6edf3availableTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3newClientTb4b4b4) Tb4b4b4{
Te6edf3_accept_clientTb4b4b4(Te6edf3newClientTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e// Client mode reconnection (with WiFi check + exponential backoff)
Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_CLIENT Tff7b72&Tff7b72& Te6edf3_num_clients Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tffa657uint32_t Te6edf3now Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3now Tff7b72- Te6edf3_last_reconnect Tff7b72>Tff7b72= Te6edf3_reconnect_intervalTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3statusTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= Te6edf3WL_CONNECTEDTb4b4b4) Tb4b4b4{
Te6edf3_connect_clientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// WiFi not connected — skip TCP attempt, just update timer
Te6edf3_last_reconnect Tff7b72= Te6edf3nowTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// Send keepalive (empty HDLC frames) to prevent read timeout on both sides
Tff7b72if Tb4b4b4(Te6edf3_num_clients Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
Tffa657uint32_t Te6edf3now Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3now Tff7b72- Te6edf3_last_keepalive Tff7b72>Tff7b72= Te6edf3TCP_IF_KEEPALIVE_INTERVALTb4b4b4) Tb4b4b4{
Te6edf3_last_keepalive Tff7b72= Te6edf3nowTb4b4b4;
Tffa657uint8_t Te6edf3kaTb4b4b4[Tb4b4b4] Tff7b72= Tb4b4b4{ Te6edf3HDLC_FLAGTb4b4b4, Te6edf3HDLC_FLAG Tb4b4b4}Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72&Tff7b72& Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3kaTb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// Process incoming data from all active clients
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tff7b72continueTb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3_cleanup_clientTb4b4b4(Te6edf3iTb4b4b4, Ta5d6ff"Ta5d6ffdisconnectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72continueTb4b4b4;
Tb4b4b4}
T8b949e// Check read timeout (0 = disabled)
Tff7b72if Tb4b4b4(Te6edf3_read_timeout Tff7b72> T79c0ff0 Tff7b72&Tff7b72&
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72> T79c0ff0 Tff7b72&Tff7b72&
Tb4b4b4(Te6edf3millisTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activityTb4b4b4) Tff7b72> Te6edf3_read_timeoutTb4b4b4) Tb4b4b4{
Te6edf3_cleanup_clientTb4b4b4(Te6edf3iTb4b4b4, Ta5d6ff"Ta5d6ffread timeoutTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72continueTb4b4b4;
Tb4b4b4}
T8b949e// Read available bytes and deframe
Tff7b72while Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3availableTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3byte Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3readTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_hdlc_deframeTb4b4b4(Te6edf3iTb4b4b4, Te6edf3byteTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// ─── Stats ───────────────────────────────────────────────────────────────
Tffa657int Te6edf3clientCountTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_num_clientsTb4b4b4; Tb4b4b4}
Tffa657bool Te6edf3isStartedTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_startedTb4b4b4; Tb4b4b4}
Tffa657bool Te6edf3isConnectedTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_num_clients Tff7b72> T79c0ff0Tb4b4b4; Tb4b4b4}
Tffa657void Te6edf3setReadTimeoutTb4b4b4(Tffa657uint32_t Te6edf3timeout_msTb4b4b4) Tb4b4b4{ Te6edf3_read_timeout Tff7b72= Te6edf3timeout_msTb4b4b4; Tb4b4b4}
Te6edf3protectedTff7b72:
T8b949e// ─── RNS InterfaceImpl: outgoing packet from RNS Transport ───────────────
Te6edf3virtual Tffa657void Te6edf3send_outgoingTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3dataTb4b4b4) Te6edf3override Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_started Tff7b72|Tff7b72| Te6edf3_num_clients Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTb4b4b4;
T8b949e// HDLC frame the data
Tffa657uint8_t Te6edf3frame_bufTb4b4b4[Te6edf3TCP_IF_HW_MTU Tff7b72* T79c0ff2 Tff7b72+ T79c0ff4Tb4b4b4]Tb4b4b4; T8b949e// worst case: every byte escaped + 2 flags
Tffa657uint16_t Te6edf3flen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3HDLC_FLAGTb4b4b4;
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3b Tff7b72= Te6edf3dataTb4b4b4.Te6edf3dataTb4b4b4(Tb4b4b4)Tb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3b Tff7b72=Tff7b72= Te6edf3HDLC_FLAG Tff7b72|Tff7b72| Te6edf3b Tff7b72=Tff7b72= Te6edf3HDLC_ESCTb4b4b4) Tb4b4b4{
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3HDLC_ESCTb4b4b4;
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3b Tff7b72^ Te6edf3HDLC_ESC_MASKTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3bTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3flen Tff7b72>Tff7b72= Tff7b72sizeofTb4b4b4(Te6edf3frame_bufTb4b4b4) Tff7b72- T79c0ff4Tb4b4b4) Tff7b72breakTb4b4b4; T8b949e// safety
Tb4b4b4}
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3HDLC_FLAGTb4b4b4;
T8b949e// Send to all connected clients EXCEPT the one that sent this packet.
T8b949e// v1.0.10: Echo prevention — if this send_outgoing was triggered by
T8b949e// Transport forwarding a packet received from client N, skip client N
T8b949e// to prevent echo-back that floods TCP buffers and stalls resource transfers.
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3i Tff7b72=Tff7b72= Te6edf3_last_rx_client_idxTb4b4b4) Tb4b4b4{
Tff7b72continueTb4b4b4; T8b949e// Don't echo back to sender
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72&Tff7b72& Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tffa657size_t Te6edf3written Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3frame_bufTb4b4b4, Te6edf3flenTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3written Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3_cleanup_clientTb4b4b4(Te6edf3iTb4b4b4, Ta5d6ff"Ta5d6ffwrite failedTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3written Tff7b72< Te6edf3flenTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] PARTIAL write to client %d: %u/%u bytesTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3iTb4b4b4, Tb4b4b4(Tffa657unsignedTb4b4b4)Te6edf3writtenTb4b4b4, Tb4b4b4(Tffa657unsignedTb4b4b4)Te6edf3flenTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3yieldTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// feed WDT between TCP writes and RNS processing
T8b949e// Post-send housekeeping
Te6edf3InterfaceImplTff7b72:Tff7b72:Te6edf3handle_outgoingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// ─── RNS InterfaceImpl: incoming packet to RNS Transport ─────────────────
Te6edf3virtual Tffa657void Te6edf3handle_incomingTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3dataTb4b4b4) Te6edf3override Tb4b4b4{
Te6edf3TRACEFTb4b4b4(Ta5d6ff"Ta5d6ffTcpInterface.handle_incoming: (%u bytes)Ta5d6ff"Tb4b4b4, Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3InterfaceImplTff7b72:Tff7b72:Te6edf3handle_incomingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3privateTff7b72:
T8b949e// ─── Cleanup a client slot, freeing all lwIP resources ───────────────────
Tffa657void Te6edf3_cleanup_clientTb4b4b4(Tffa657int Te6edf3idxTb4b4b4, Tff7b72const Tffa657charTff7b72* Te6edf3reasonTb4b4b4) Tb4b4b4{
Te6edf3TcpClientTff7b72& Te6edf3c Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3idxTb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3cTb4b4b4.Te6edf3activeTb4b4b4) Tff7b72returnTb4b4b4;
Tffa657uint32_t Te6edf3heap_before Tff7b72= Te6edf3ESPTb4b4b4.Te6edf3getFreeHeapTb4b4b4(Tb4b4b4)Tb4b4b4;
T8b949e// Set SO_LINGER with timeout 0: forces RST instead of FIN,
T8b949e// which skips TIME_WAIT and immediately frees the lwIP PCB
T8b949e// and all associated TCP send/receive buffers (~2-4 KB each).
Tffa657int Te6edf3fd Tff7b72= Te6edf3cTb4b4b4.Te6edf3clientTb4b4b4.Te6edf3fdTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fd Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72struct T56d364linger Te6edf3linTb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_onoff Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_linger Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_LINGERTb4b4b4, Tff7b72&Te6edf3linTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3linTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3cTb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3cTb4b4b4.Te6edf3client Tff7b72= Te6edf3WiFiClientTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// Release any residual shared_ptr state
Te6edf3cTb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_num_clientsTff7b72-Tff7b72-Tb4b4b4;
Tffa657uint32_t Te6edf3heap_after Tff7b72= Te6edf3ESPTb4b4b4.Te6edf3getFreeHeapTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d %s (heap: %u -> %u, delta: %+d)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3idxTb4b4b4, Te6edf3reasonTb4b4b4, Te6edf3heap_beforeTb4b4b4, Te6edf3heap_afterTb4b4b4,
Tb4b4b4(Tffa657intTb4b4b4)Tb4b4b4(Te6edf3heap_after Tff7b72- Te6edf3heap_beforeTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// ─── HDLC byte-level deframing ──────────────────────────────────────────
Tffa657void Te6edf3_hdlc_deframeTb4b4b4(Tffa657int Te6edf3idxTb4b4b4, Tffa657uint8_t Te6edf3byteTb4b4b4) Tb4b4b4{
Te6edf3TcpClientTff7b72& Te6edf3c Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3idxTb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_FLAGTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72&Tff7b72& Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// v1.0.12: If the frame exceeded the buffer, drop it entirely
T8b949e// instead of delivering a truncated/corrupt packet to Transport.
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3truncatedTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] DROPPED oversized frame from client %d (>%d bytes, buffered %u)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3idxTb4b4b4, Te6edf3TCP_IF_HW_MTUTb4b4b4, Te6edf3cTb4b4b4.Te6edf3rxlenTb4b4b4)Tb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// End of frame — deliver to RNS
T8b949e// v1.0.10: Set _last_rx_client_idx so send_outgoing() can
T8b949e// skip echoing this packet back to the client that sent it.
T8b949e// The entire call chain (handle_incoming → Transport::inbound
T8b949e// → transmit → send_outgoing) is synchronous, so this is safe.
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Td2a8ffdataTb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4, Te6edf3cTb4b4b4.Te6edf3rxlenTb4b4b4)Tb4b4b4;
Te6edf3_last_rx_client_idx Tff7b72= Te6edf3idxTb4b4b4;
Te6edf3handle_incomingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Te6edf3_last_rx_client_idx Tff7b72= T79c0ff-1Tb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72= Tffa657trueTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3in_frameTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3escapeTb4b4b4) Tb4b4b4{
Te6edf3byte Tff7b72^Tff7b72= Te6edf3HDLC_ESC_MASKTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72< Te6edf3TCP_IF_HW_MTUTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4[Te6edf3cTb4b4b4.Te6edf3rxlenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_ESCTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72< Te6edf3TCP_IF_HW_MTUTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4[Te6edf3cTb4b4b4.Te6edf3rxlenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// ─── Accept a new server-mode client ─────────────────────────────────────
Tffa657void Te6edf3_accept_clientTb4b4b4(Te6edf3WiFiClientTff7b72& Te6edf3newClientTb4b4b4) Tb4b4b4{
T8b949e// Find a free slot
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tb4b4b4{
T8b949e// Defensive: force-release any residual lwIP resources in this slot
T8b949e// before assigning the new client (prevents PCB/buffer leaks)
Tffa657int Te6edf3fd Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3fdTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fd Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72struct T56d364linger Te6edf3linTb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_onoff Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_linger Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_LINGERTb4b4b4, Tff7b72&Te6edf3linTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3linTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3WiFiClientTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// Reset to clean state
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3newClientTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_WRITE_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_num_clientsTff7b72+Tff7b72+Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d connected from %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3iTb4b4b4, Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3remoteIPTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e// No free slots — reject
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Max clients reached, rejecting connectionTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3newClientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// ─── Client-mode outbound connection ─────────────────────────────────────
Tffa657void Te6edf3_connect_clientTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_target_hostTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] No target host configured for client modeTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Te6edf3WiFiClient Te6edf3clientTb4b4b4;
Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_CONNECT_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Tffa657bool Te6edf3connected Tff7b72= Tffa657falseTb4b4b4;
T8b949e// Try cached IP first (avoids DNS lookup on every reconnect)
Tff7b72if Tb4b4b4(Te6edf3_resolved_ip Tff7b72!Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connecting to %s:%d (cached IP)...Tffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Te6edf3connected Tff7b72= Te6edf3clientTb4b4b4.Te6edf3connectTb4b4b4(Te6edf3_resolved_ipTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3connectedTb4b4b4) Tb4b4b4{
T8b949e// Cached IP failed — clear cache and try fresh DNS
Te6edf3_resolved_ip Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Cached IP failed, retrying with DNSTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3connectedTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connecting to %s:%d (DNS)...Tffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Te6edf3IPAddress Te6edf3resolvedTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3hostByNameTb4b4b4(Te6edf3_target_hostTb4b4b4, Te6edf3resolvedTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3_resolved_ip Tff7b72= Te6edf3resolvedTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Resolved %s -> %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3resolvedTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3connected Tff7b72= Te6edf3clientTb4b4b4.Te6edf3connectTb4b4b4(Te6edf3resolvedTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] DNS failed for %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3connectedTb4b4b4) Tb4b4b4{
Te6edf3clientTb4b4b4.Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_WRITE_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3clientTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_num_clients Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3_consecutive_failures Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_reconnect_interval Tff7b72= Te6edf3TCP_IF_RECONNECT_MINTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connected to backbone at %s:%dTffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3_consecutive_failuresTff7b72+Tff7b72+Tb4b4b4;
T8b949e// Exponential backoff: 10s -> 20s -> 40s -> 80s -> 120s (max)
Te6edf3_reconnect_interval Tff7b72= Te6edf3_reconnect_interval Tff7b72* T79c0ff2Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3_reconnect_interval Tff7b72> Te6edf3TCP_IF_RECONNECT_MAXTb4b4b4) Tb4b4b4{
Te6edf3_reconnect_interval Tff7b72= Te6edf3TCP_IF_RECONNECT_MAXTb4b4b4;
Tb4b4b4}
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Failed to connect to %s:%d (attempt %d, next retry in %ds)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4, Te6edf3_consecutive_failuresTb4b4b4,
Te6edf3_reconnect_interval Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_last_reconnect Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// ─── Member variables ────────────────────────────────────────────────────
Te6edf3TcpIfMode Te6edf3_modeTb4b4b4;
Tffa657uint16_t Te6edf3_portTb4b4b4;
Tffa657char Te6edf3_target_hostTb4b4b4[T79c0ff64Tb4b4b4]Tb4b4b4;
Tffa657uint16_t Te6edf3_target_portTb4b4b4;
Te6edf3WiFiServerTff7b72* Te6edf3_serverTb4b4b4;
Te6edf3TcpClient Te6edf3_clientsTb4b4b4[Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4]Tb4b4b4;
Tffa657int Te6edf3_num_clientsTb4b4b4;
Tffa657uint32_t Te6edf3_last_reconnectTb4b4b4;
Tffa657uint32_t Te6edf3_last_keepaliveTb4b4b4;
Tffa657uint32_t Te6edf3_reconnect_intervalTb4b4b4;
Tffa657uint32_t Te6edf3_read_timeoutTb4b4b4;
Te6edf3IPAddress Te6edf3_resolved_ipTb4b4b4;
Tffa657uint16_t Te6edf3_consecutive_failuresTb4b4b4;
Tffa657bool Te6edf3_startedTb4b4b4;
Tffa657int Te6edf3_last_rx_client_idx Tff7b72= T79c0ff-1Tb4b4b4; T8b949e// v1.0.10: echo prevention — tracks which client is currently delivering an inbound frame
Tb4b4b4}Tb4b4b4;
Tff7b72#Tff7b72endif T8b949e// BOUNDARY_MODE
Tff7b72#Tff7b72endif T8b949e// HAS_RNS
Tff7b72#Tff7b72endif T8b949e// TCP_INTERFACE_H
Served by rngit 1.4.2 - Generated in 0.05s